import { QueryInfo } from "./beans/Infos"
import AuthManager from "./managers/AuthManager"
import QueryManager from "./managers/QueryManager"
import { AsyncCallback } from "@kit.BasicServicesKit"

export namespace cloudDatabase {
  type FieldType = string | number | boolean | Uint8Array | Date

  export let zoneName: string

  export function zone(zone: string): DatabaseZone {
    zoneName = zone
    return new DatabaseZone()
  }

  export abstract class DatabaseObject {
    abstract naturalbase_ClassName(): string

    naturalbase_creator: string
    naturalbase_version: string
  }

  export class DatabaseZone {
    query<T extends DatabaseObject>(condition: DatabaseQuery<T>): Promise<T[]>
    query<T extends DatabaseObject>(condition: DatabaseQuery<T>, callback: AsyncCallback<T[]>): void

    async query<T extends DatabaseObject>(condition: DatabaseQuery<T>,
      callback?: AsyncCallback<T[]>): Promise<T[] | void> {
      await AuthManager.init()
      const result = await QueryManager.query(zoneName, condition, QueryInfo.schemaVersion) as T[]
      if (callback) {
        callback(undefined, result)
        return
      } else {
        return Promise.resolve(result)
      }
    }

    calculateQuery<T extends DatabaseObject>(condition: DatabaseQuery<T>, fieldName: string, calculate: QueryCalculate): Promise<number>;
    calculateQuery<T extends DatabaseObject>(condition: DatabaseQuery<T>, fieldName: string, calculate: QueryCalculate, callback: AsyncCallback<number>): void;

    async calculateQuery<T extends DatabaseObject>(condition: DatabaseQuery<T>, fieldName: string, calculate: QueryCalculate, callback?: AsyncCallback<number>): Promise<number | void> {
      await AuthManager.init()
      const finalCondition = condition.calculation(fieldName, calculate)
      const result = await QueryManager.query(zoneName, finalCondition, QueryInfo.schemaVersion, calculate) as number
      if (callback) {
        callback(undefined, result)
        return
      } else {
        return Promise.resolve(result)
      }
    }

    //
    //   upsert<T extends DatabaseObject>(objectList: T[] | T): Promise<number>;
    //
    //   upsert<T extends DatabaseObject>(objectList: T[] | T, callback: AsyncCallback<number>): void;
    //
    //   delete<T extends DatabaseObject>(objectList: T[] | T): Promise<number>;
    //
    //   delete<T extends DatabaseObject>(objectList: T[] | T, callback: AsyncCallback<number>): void;
  }

  export enum QueryConditionType {
    EQUAL_TO = "EqualTo",
    NOT_EQUAL_TO = "NotEqualTo",
    BEGINS_WITH = "BeginWith",
    ENDS_WITH = "EndWith",
    CONTAINS = "Contain",
    GREATER_THAN = "GreaterThan",
    GREATER_THAN_OR_EQUAL_TO = "GreaterThanOrEqualTo",
    LESS_THAN = "LessThan",
    LESS_THAN_OR_EQUAL_TO = "LessThanOrEqualTo",
    IN = "In",
    IS_NULL = "IsNull",
    IS_NOT_NULL = "IsNotNull",
    ORDER_BY_ASC = "OrderByAsc",
    ORDER_BY_DESC = "OrderByDesc",
    LIMIT = "Limit",
    BEGIN_GROUP = "BeginGroup",
    END_GROUP = "EndGroup",
    OR = "Or",
    AND = "And",
    AVERAGE = "AVG",
    SUM = "SUM",
    MAX = "MAX",
    MIN = "MIN",
    COUNT = "COUNT"
  }

  export enum QueryCalculate {
    AVERAGE = 0,
    SUM = 1,
    MAXIMUM = 2,
    MINIMUM = 3,
    COUNT = 4
  }

  export interface QueryConditionValueLimit {
    number: number
    offset?: number
  }

  export interface QueryCondition {
    conditionType: QueryConditionType
    fieldName?: string
    value?: FieldType | QueryConditionValueLimit | FieldType[]
  }

  export class DatabaseQuery<T extends DatabaseObject> {
    public entityClass: new () => T

    // todo 做成包外不可访问
    _queryConditions: QueryCondition[] = []

    constructor(entityClass: new () => T) {
      this.entityClass = entityClass
    }

    equalTo(fieldName: string, value: FieldType): DatabaseQuery<T> {
      this._queryConditions.push({
        conditionType: QueryConditionType.EQUAL_TO,
        fieldName: fieldName,
        value: value
      })
      return this
    }

    notEqualTo(fieldName: string, value: FieldType): DatabaseQuery<T> {
      this._queryConditions.push({
        conditionType: QueryConditionType.NOT_EQUAL_TO,
        fieldName: fieldName,
        value: value
      })
      return this
    }

    beginsWith(fieldName: string, value: FieldType): DatabaseQuery<T> {
      this._queryConditions.push({
        conditionType: QueryConditionType.BEGINS_WITH,
        fieldName: fieldName,
        value: value
      })
      return this
    }

    endsWith(fieldName: string, value: FieldType): DatabaseQuery<T> {
      this._queryConditions.push({
        conditionType: QueryConditionType.ENDS_WITH,
        fieldName: fieldName,
        value: value
      })
      return this
    }

    contains(fieldName: string, value: FieldType): DatabaseQuery<T> {
      this._queryConditions.push({
        conditionType: QueryConditionType.CONTAINS,
        fieldName: fieldName,
        value: value
      })
      return this
    }

    greaterThan(fieldName: string, value: FieldType): DatabaseQuery<T> {
      this._queryConditions.push({
        conditionType: QueryConditionType.GREATER_THAN,
        fieldName: fieldName,
        value: value
      })
      return this
    }

    greaterThanOrEqualTo(fieldName: string, value: FieldType): DatabaseQuery<T> {
      this._queryConditions.push({
        conditionType: QueryConditionType.GREATER_THAN_OR_EQUAL_TO,
        fieldName: fieldName,
        value: value
      })
      return this
    }

    lessThan(fieldName: string, value: FieldType): DatabaseQuery<T> {
      this._queryConditions.push({
        conditionType: QueryConditionType.LESS_THAN,
        fieldName: fieldName,
        value: value
      })
      return this
    }

    lessThanOrEqualTo(fieldName: string, value: FieldType): DatabaseQuery<T> {
      this._queryConditions.push({
        conditionType: QueryConditionType.LESS_THAN_OR_EQUAL_TO,
        fieldName: fieldName,
        value: value
      })
      return this
    }

    in(fieldName: string, values: FieldType[]): DatabaseQuery<T> {
      this._queryConditions.push({
        conditionType: QueryConditionType.IN,
        fieldName: fieldName,
        value: values
      })
      return this
    }

    isNull(fieldName: string): DatabaseQuery<T> {
      this._queryConditions.push({
        conditionType: QueryConditionType.IS_NULL,
        fieldName: fieldName
      })
      return this
    }

    isNotNull(fieldName: string): DatabaseQuery<T> {
      this._queryConditions.push({
        conditionType: QueryConditionType.IS_NOT_NULL,
        fieldName: fieldName
      })
      return this
    }

    orderByAsc(fieldName: string): DatabaseQuery<T> {
      this._queryConditions.push({
        conditionType: QueryConditionType.ORDER_BY_ASC,
        fieldName: fieldName
      })
      return this
    }
    //
    // orderByDesc(fieldName: string): DatabaseQuery<T> {
    //   this._queryConditions.push({
    //     conditionType: QueryConditionType.ORDER_BY_DESC,
    //     fieldName: fieldName
    //   })
    //   return this
    // }

    limit(number: number, offset?: number): DatabaseQuery<T> {
      this._queryConditions.push({
        conditionType: QueryConditionType.LIMIT,
        value: {
          number: number,
          offset: offset
        } as QueryConditionValueLimit
      })
      return this
    }

    beginGroup(): DatabaseQuery<T> {
      this._queryConditions.push({
        conditionType: QueryConditionType.BEGIN_GROUP
      })
      return this
    }

    endGroup(): DatabaseQuery<T> {
      this._queryConditions.push({
        conditionType: QueryConditionType.END_GROUP
      })
      return this
    }

    or(): DatabaseQuery<T> {
      this._queryConditions.push({
        conditionType: QueryConditionType.OR
      })
      return this
    }

    and(): DatabaseQuery<T> {
      return this
    }

    calculation(fieldName: string, calculate: QueryCalculate): DatabaseQuery<T> {
      let conditionType: QueryConditionType
      switch(calculate) {
        case QueryCalculate.AVERAGE: {
          conditionType = QueryConditionType.AVERAGE
          break
        }

        case QueryCalculate.SUM: {
          conditionType = QueryConditionType.SUM
          break
        }

        case QueryCalculate.MAXIMUM: {
          conditionType = QueryConditionType.MAX
          break
        }

        case QueryCalculate.MINIMUM: {
          conditionType = QueryConditionType.MIN
          break
        }

        case QueryCalculate.COUNT: {
          conditionType = QueryConditionType.COUNT
          break
        }

        default: {
          break
        }
      }
      this._queryConditions.push({
        conditionType: conditionType,
        fieldName: fieldName
      })
      return this
    }
  }
}

export default cloudDatabase;
